using System;

namespace SharpChannel.Core.Packet
{
    public sealed class RingBufferQueue<T> : IBufferQueue<T> where T : IEquatable<T>
    {
        #region IBufferQueue implementation

        public void EnQueue(T[] array, int offset = 0, int size = -1)
        {
            if (size <= 0)
                size = array.Length - offset;
            if (size <= 0)
                return;
            while (RemainSize() < size)
                Expand();
            if (head <= tail)
            {
                if (buffer.Length - tail >= size)
                {
                    Array.Copy(array, offset, buffer, tail, size);
                    tail += size;
                }
                else
                {
                    Array.Copy(array, offset, buffer, tail, buffer.Length - tail);
                    Array.Copy(array, offset + buffer.Length - tail, buffer, 0, size + tail - buffer.Length);
                    tail = size + tail - buffer.Length;
                }
            }
            else
            {
                Array.Copy(array, offset, buffer, tail, size);
                tail += size;
            }

        }

        public T[] DeQueue(int offset = 0, int size = -1)
        {
            T[] result = Slice(offset, size);
            if (result != null)
            {
                head = (head + result.Length + offset) % buffer.Length;
                return result;
            }
            else
            {
                return null;
            }
        }

        public T[] Slice(int offset = 0, int size = -1)
        {
            if (size < 0)
                size = Length - offset;
            size = Math.Min(size, Length - offset);
            if (size <= 0)
                return null;
            T[] result = new T[size];
            int start = (head + offset) % buffer.Length;
            int end = (head + offset + size) % buffer.Length;
            if (end > start)
            {
                Array.Copy(buffer, start, result, 0, size);
            }
            else
            {
                Array.Copy(buffer, start, result, 0, buffer.Length - start);
                Array.Copy(buffer, 0, result, buffer.Length - start, size + start - buffer.Length);
            }
            return result;
        }

        public T At(int index)
        {
            return buffer[(head + index) % buffer.Length];
        }

        public void Clear()
        {
            head = tail = 0;
        }

        #endregion


        #region IUnique implementation

        public long Id
        {
            get;
            set;
        }

        public override string ToString()
        {
            var slice = Slice();
            // ReSharper disable once AssignNullToNotNullAttribute
            return System.Text.Encoding.UTF8.GetString(slice == null ? System.Text.Encoding.UTF8.GetBytes("null") : slice as byte[]);
        }

        public int ReadableSize()
        {
            return Length;
        }

        #endregion

        internal RingBufferQueue(int size = 1024 * 1024)
        {
            buffer = new T[size];
        }

        public int Length
        {
            get
            {
                if (head == tail)
                    return 0;
                else if (head < tail)
                    return tail - head;
                else
                    return tail + buffer.Length - head;
            }
        }

        private int RemainSize()
        {
            return buffer.Length - Length - 1;
        }

        private void Expand(int size = -1)
        {
            int newBufferSize = size < 0 ? buffer.Length * 2 : buffer.Length + size;
            T[] newBuffer = new T[newBufferSize];
            if (head <= tail)
            {
                Array.Copy(buffer, head, newBuffer, 0, tail - head);
            }
            else
            {
                Array.Copy(buffer, head, newBuffer, 0, buffer.Length - head);
                Array.Copy(buffer, 0, newBuffer, buffer.Length - head, tail);
            }
            head = 0;
            tail = Length;
            buffer = newBuffer;
        }

        private T[] buffer;
        private int head, tail;
    }
}
